/**
 ** Copyright (c) 2010, GE Healthcare
 ** All rights reserved.
 **
 ** Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
 ** following conditions are met:
 ** 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the
 **    following disclaimer.
 ** 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
 **    disclaimer in the documentation and/or other materials provided with the distribution.
 ** 3. Neither the name of the GE Healthcare nor the names of its contributors may be used to endorse or promote products
 **    derived from this software without specific prior written permission.
 **
 ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
 ** INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 ** DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 ** OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 ** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 ** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/**
 * This software was created by its contributors for NHINDirect.org for the sole purpose of demonstrating the
 * viability of using Integrating the Healthcare Enterprise (IHE) in accordance with the proposed
 * IHE NHIN Direct Specfication.
 *
 * References:
 * http://nhindirect.org/
 * http://nhindirect.org/NHIN+Direct+Abstract+Model
 * http://nhindirect.org/IHE+NHIN+Direct+Specification
 *
 * Contributors:
 * mailto:/matt.potter@ge.com
 */
package org.nhindirect;

import org.apache.log4j.Logger;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.emf.ecore.xmi.XMLResource;
import org.openhealthtools.ihe.common.ebxml._3._0.lcm.LCMPackage;
import org.openhealthtools.ihe.common.ebxml._3._0.lcm.SubmitObjectsRequestType;
import org.openhealthtools.ihe.xdm.importer.PortableMediaImporter;
import org.openhealthtools.ihe.xds.document.DocumentDescriptor;
import org.openhealthtools.ihe.xds.document.XDSDocument;
import org.openhealthtools.ihe.xds.document.XDSDocumentFromStream;
import org.openhealthtools.ihe.xds.metadata.DocumentEntryType;
import org.openhealthtools.ihe.xds.metadata.ProvideAndRegisterDocumentSetType;
import org.openhealthtools.ihe.xds.metadata.SubmissionSetType;
import org.openhealthtools.ihe.xds.source.SubmitTransactionData;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

/**
 * Demonstrates how the OpenHealthTools IHE Profiles library can be used to convert between IHE XDM Archives and
 * the IHE Provide And Register transactions specified with XDR.
 * <p/>
 * TODO doesn't support IHE XDM's multipart/related.
 */
public class XDMArchiver {

    private static final Logger LOGGER = Logger.getLogger(XDMArchiver.class);

    static private String XDM_FILENAME_METADATA = "METADATA.XML";
    static private String XDM_DIRSPEC_SUBMISSIONROOT = "IHE_XDM";

    /**
     * TODO this needs to load from a complete mime map resource, demo version is just an example for some common types
     */
    static Map<String, String> MIMEMAP = new HashMap();

    static {
        MIMEMAP.put("text/xml", "xml");
        MIMEMAP.put("application/xml", "xml");
        MIMEMAP.put("application/pdf", "pdf");
        MIMEMAP.put("text/plain", "txt");
    }

    /**
     * Reads an XDM ZIP archive and returns a set of XDS submissions.
     */
    static public SubmitTransactionData[] loadXDMArchive(File archiveFile) throws Exception {
        Map<String, SubmitTransactionData> results = new HashMap<String, SubmitTransactionData>();
        ZipFile zipFile = null;

        try {
            zipFile = new ZipFile(archiveFile, ZipFile.OPEN_READ);
            Enumeration<? extends ZipEntry> zipEntries = zipFile.entries();
            ZipEntry zipEntry = null;

            // load the ZIP archive into memory
            while (zipEntries.hasMoreElements()) {
                zipEntry = zipEntries.nextElement();
                if (!zipEntry.isDirectory() && zipEntry.getName().startsWith(XDM_DIRSPEC_SUBMISSIONROOT)) {
                    String subsetDirspec = getSubmissionSetDirspec(zipEntry.getName());

                    // we've found a new submission set
                    if (!results.containsKey(subsetDirspec)) {

                        // extract METADATA.XML
                        ZipEntry metadataEntry = getXDMZipEntry(zipFile, subsetDirspec, XDM_FILENAME_METADATA);
                        if (metadataEntry == null) {
                            LOGGER.warn("XDM submission set folder '" + subsetDirspec + "' has no metadata file: " + XDM_FILENAME_METADATA);
                        } else {

                            InputStream in = zipFile.getInputStream(metadataEntry);
                            ByteArrayOutputStream baos = new ByteArrayOutputStream();
                            int bytesRead = 0;
                            byte[] buffer = new byte[2048];
                            while ((bytesRead = in.read(buffer)) != -1) {
                                baos.write(buffer, 0, bytesRead);
                            }
                            in.close();

                            LCMPackage pkg = org.openhealthtools.ihe.common.ebxml._3._0.lcm.LCMPackage.eINSTANCE;
                            XMLResource resources = (XMLResource) (new org.openhealthtools.ihe.common.ebxml._3._0.lcm.util.LCMResourceFactoryImpl().createResource(
                                    URI.createURI(org.openhealthtools.ihe.common.ebxml._3._0.lcm.LCMPackage.eNS_URI)));
                            ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
                            resources.load(bais, null);
                            EList list = resources.getContents();
                            Object o = list.get(0);

                            if (o instanceof org.openhealthtools.ihe.common.ebxml._3._0.lcm.DocumentRoot) {
                                org.openhealthtools.ihe.common.ebxml._3._0.lcm.DocumentRoot docRoot = (org.openhealthtools.ihe.common.ebxml._3._0.lcm.DocumentRoot) o;
                                SubmitObjectsRequestType soRequestType = docRoot.getSubmitObjectsRequest();

                                PortableMediaImporter importer = new PortableMediaImporter();
                                ProvideAndRegisterDocumentSetType prType = importer.importXDMMetadata(soRequestType);
                                SubmitTransactionData txnData = new SubmitTransactionData();
                                txnData.getMetadata().setSubmissionSet((SubmissionSetType) EcoreUtil.copy(prType.getSubmissionSet()));

                                for (int i = 0; i < prType.getDocumentEntry().size(); i++) {
                                    DocumentEntryType xdsDocumentEntry = (DocumentEntryType) prType.getDocumentEntry().get(i);
                                    String subsetFilespec = xdsDocumentEntry.getUri();
                                    xdsDocumentEntry.setUri(null); // is not a valid 'URI' value for a repository submission
                                    ZipEntry docZipEntry = getXDMZipEntry(zipFile, subsetDirspec, subsetFilespec);
                                    if (docZipEntry != null) {
                                        InputStream docStream = zipFile.getInputStream(docZipEntry);
                                        XDSDocument xdsDocument =
                                                new XDSDocumentFromStream(DocumentDescriptor.getDocumentDescriptorForMimeType(xdsDocumentEntry.getMimeType()), docStream);
                                        xdsDocument.setDocumentEntryUUID(xdsDocumentEntry.getEntryUUID());
                                        xdsDocument.setDocumentUniqueId(xdsDocumentEntry.getUniqueId());
                                        xdsDocument.setRepositoryUniqueId(xdsDocumentEntry.getRepositoryUniqueId());
                                        txnData.getDocList().add(xdsDocument);
                                        txnData.getMetadata().getDocumentEntry().add(EcoreUtil.copy(xdsDocumentEntry));
                                    } else {
                                        LOGGER.error(XDM_FILENAME_METADATA + " in XDM submission folder " + subsetDirspec +
                                                " has XDSDocumentEntry.URI '" + xdsDocumentEntry.getUri() + "' that cannot be found in ZIP");
                                    }
                                }

                                results.put(subsetDirspec, txnData);
                            }

                        }
                    }
                }
            }
        } finally {
            if (zipFile != null) {
                zipFile.close();
            }
        }

        return results.values().toArray(new SubmitTransactionData[]{});
    }

    /**
     * Tries accessing an XDM file inside ZIP using both MS-DOS and UNIX separators
     */
    static private ZipEntry getXDMZipEntry(ZipFile zipFile, String subsetDirspec, String subsetFilespec) {
        ZipEntry result = null;
        String zipFilespec = XDM_DIRSPEC_SUBMISSIONROOT + "\\" + subsetDirspec + "\\" + subsetFilespec.replace('/', '\\');
        result = zipFile.getEntry(zipFilespec);
        if (result == null) {
            zipFilespec = zipFilespec.replace('\\', '/');
            result = zipFile.getEntry(zipFilespec);
        }
        return result;
    }

    /**
     * Given a full ZipEntry filespec, extracts the name of the folder (if present) under the IHE_XDM root
     * specified by IHE XDM.
     */
    static private String getSubmissionSetDirspec(String zipEntryName) {
        String result = null;
        if (zipEntryName != null) {
            String[] components = zipEntryName.split("[\\\\/]");
            if (components[0].equals(XDM_DIRSPEC_SUBMISSIONROOT)) {
                result = components[1];
            }
        }
        return result;
    }


}
